package com.nulldev.util.web.YouTubeExtractor.signatures.extractors;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Returnable;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.YouTubeExtractor.signatures.ExtractedSignatureData;
import com.nulldev.util.web.YouTubeExtractor.signatures.extractor.Extractor;

/**
 * The software extractor, designed for non Nashorn JRE's. <br>
 * Slightly faster than Nashorn itself. <br>
 * Probably will break in a week. <br>
 * 
 * @author null
 * @version 1.0.5
 */
public class SoftwareExtractor extends Extractor {

	/*
	 * FUCKING FIXED FINALLY, JESUS CHRIST IS A JAVASCRIPT A PIECE OF SHIT. Hint:
	 * splice(). Yes, thank you Java for copyOfRange.
	 * 
	 * Changelog: 1.0: - Initial release. 1.0.1: - Now works again. 1.0.2: - Code
	 * cleanup on init. 1.0.3: - Optimizations - Faster Execution - Better
	 * Instruction Queue Performance 1.0.4: - Reverted some Optimizations - Fixed it
	 * - Did some Optimizations 1.0.5: - Added support for []-based JS invokes. -
	 * Fixed it, again. - Fixed potential lockups in the decrypt() function.
	 * 
	 * NOTICE: If Cipher v9 was to ever exist, this decipherer will only support v8
	 * and older. In other words, this extractor is deprecated, once that happens.
	 * Only emergency maintenance will be done. Of course, if my assumptions are
	 * correct.
	 */

	private static final Logger log = LoggerFactory.getLoggerD(Arguments.hasArgument("--debug", "--trace", "--SoftwareExtractor.debug"), true);
	private volatile String revFuncName, spliceFuncName, interChangeFuncName;
	private volatile String[] actions;

	private Throwable ex;

	public SoftwareExtractor(final ExtractedSignatureData data) {
		super(data);
//		log.traceEnabled = true;
		try {
			this.loadPayload(data);
		} catch (Throwable ex) {
			this.ex = ex;
		}
	}

	private void loadPayload(ExtractedSignatureData data) {
		log.debug("SoftwareExtractor for YouTubeExtractor, v1.0.5.");
		final String pCode = this.getCode().replaceAll("(\\s\\s\\s*)", "").replaceAll("\n", "");
		try {
			final String intfunc = StringsUtil.substringBetweenO(pCode, "={", "};");
			final String[] funcs = intfunc.split(Pattern.quote("},"));
			for (final String func : funcs) {
				if (func.contains("function(a){") && func.contains("a.reverse()")) {
					try {
						this.revFuncName = func.substring(0, func.indexOf(":"));
					} catch (Exception e) {
						throw new RuntimeException("Unable to find reverse()!", e);
					}
				} else if (func.contains("function(a,b){") && func.contains("%")) {
					try {
						this.interChangeFuncName = func.substring(0, func.indexOf(":"));
					} catch (Exception e) {
						throw new RuntimeException("Unable to find interchange()!", e);
					}
				} else if (func.contains("function(a,b){") && func.contains("splice(")) {
					try {
						this.spliceFuncName = func.substring(0, func.indexOf(":"));
					} catch (Exception e) {
						throw new RuntimeException("Unable to find splice()!", e);
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException("Unable to find functions!", e);
		}
		log.debug("reverse() -> " + this.revFuncName);
		log.debug("interchange() -> " + this.interChangeFuncName);
		log.debug("splice() -> " + this.spliceFuncName);
		final String al = StringsUtil.substringBetweenO(this.getCode(), this.getFunctionName() + "=function(a){", "};");
		this.actions = al.split(Pattern.quote(";"));
	}

	private String _decrypt(final String signature) {
		return this._decrypt(signature, false);
	}

	private String _decrypt(final String signature, final boolean isRetry) {
		if (signature == null)
			return null;
		if (this.ex != null)
			throw new RuntimeException(this.ex);
		String[] split = Splitters.splitString(signature, "");
		final List<String> acts = ArrayUtils.toArrayListFast(SoftwareExtractor.this.actions());
		acts.remove(0);
		acts.remove(acts.size() - 1); // remove split() and join() from actions list
		for (final String act : acts) {
			final boolean isSquareInvoke = act.contains("](") && act.endsWith(")");
			final String func = isSquareInvoke ? StringsUtil.substringBetweenO(act, "[", "]") : StringsUtil.substringBetweenO(act, ".", "(");
			final int i = MathUtil.safeParseInt(StringsUtil.substringBetweenO(act, ",", ")"), -1);
			if (i == -1 || func == null || func.length() == 0) {
				log.error("[SoftDec] Skipped a decipher action! [act=" + act + "]");
				continue;
			}
			if (func.equals(SoftwareExtractor.this.revFuncName)) {
				ArrayUtils.reverse(split);
			} else if (func.equals(SoftwareExtractor.this.interChangeFuncName)) {
				interChange(split, i);
			} else if (func.equals(SoftwareExtractor.this.spliceFuncName)) {
				split = Arrays.copyOfRange(split, i, split.length);
			} else {
				log.warn("[SoftDec] Unknown command! (command=" + func + ")");
			}
		}
		final String s = ArrayUtils.join(split, "");
		acts.clear();
		log.debug(signature + " -> " + s);
		if (s.equalsIgnoreCase(signature)) {
			log.warn("[SoftDec] No operations occured! Trying again...");
			return decrypt(signature);
		} /*
			 * else if (!s.startsWith("A") && !isRetry) { final String e = this._decrypt(s,
			 * true); final int sub = e.length() - 104; return e.substring(sub, e.length());
			 * }
			 */ else
			return s;
	}

	@Override
	public Executable<String> decryptTask(final String signature) {
		return ExecutorManager.make(new Returnable<String>() {

			@Override
			public String run() {
				return _decrypt(signature);
			}
		});
	}

	private String[] actions() {
		if (this.actions == null)
			throw new NullPointerException("actions == null!");
		return Arrays.copyOf(this.actions, this.actions.length);
	}

	private void interChange(String[] a, final int b) {
		final String c = a[0];
		a[0] = a[b % a.length];
		a[b % a.length] = c;
	}

	@Override
	public String decrypt(final String signature) {
		return _decrypt(signature);
	}

	@Override
	public String getCode() {
		return this.getExtractedData().getCode();
	}

	@Override
	public String getFunctionCode() {
		return this.getExtractedData().getFunctionCode();
	}

	@Override
	public String getFunctionName() {
		return this.getExtractedData().getFunctionName();
	}

	@Override
	public Extractor updateESD(final ExtractedSignatureData newData) {
		super.setExtractedData(newData);
		this.loadPayload(newData);
		return this;
	}

	public static void main(String[] args) {
		final SoftwareExtractor se = new SoftwareExtractor(new ExtractedSignatureData("var GB={cv:function(a){a.reverse()},"
				+ "Xb:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c},"
				+ "U4:function(a,b){a.splice(0,b)}};X=function(a){a=a.split(\"\");GB.Xb(a,60);GB.cv(a,58);GB.Xb(a,24);GB.Xb(a,13);return a.join(\"\")};",
				"var GB={cv:function(a){a.reverse()}," + "Xb:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c},"
						+ "U4:function(a,b){a.splice(0,b)}};",
				"X"));
		final String input = "qOUAOUOqAOq0QJ8wRQIgT6CjKbUmZGAoRcN3r87KN9R4l7EvZlb3MiOXBRAyuUgCIQDUMQ=c1o7a9mtDqMJMj9T0HNV6-iEb-jOHPHGgHyqQvg=w";
		// "NOxyOxOqAOq0QJ8wRAIgL1JqVAA7h8AnZQ8EDSni0ukEU89m096XTU22PsyXMqUCID4xDS3qpwRxkh1N9n495MmRlrDdDcCdK3q0lvU8nI7X";
		// bad out:
		// C7In8Uvl0q3Kd9cDdDrlRmM5X4n9N1hkxRwpq3SDx4DICUqNXysP22UTX690m98UEku0inSDE8QZnA8h7AAVqJ1LgIARw8JQ0qOAqOxOyxOM
		final String out = se.decrypt(input);
		JVM.println("OUTPUT: " + out);
		JVM.println("INPUT_LEN: " + input.length());
		JVM.println("OUTPUT_LEN: " + out.length());
//		final String out1 = se.decrypt(out);
//		JVM.println("OUTPUT1: " + out1);
//		JVM.println("OUTPUT1_LEN: " + out1.length());
	}

}
